/***************************************************************************/
/*                                                                         */
/*  pshrec.c                                                               */
/*                                                                         */
/*    FreeType PostScript hints recorder (body).                           */
/*                                                                         */
/*  Copyright 2001, 2002, 2003, 2004, 2007, 2009 by                        */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/


#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include FT_INTERNAL_CALC_H

#include "pshrec.h"
#include "pshalgo.h"

#include "pshnterr.h"

#undef  FT_COMPONENT
#define FT_COMPONENT trace_pshrec

#ifdef DEBUG_HINTER
PS_Hints ps_debug_hints = 0;
int ps_debug_no_horz_hints = 0;
int ps_debug_no_vert_hints = 0;
#endif


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                      PS_HINT MANAGEMENT                       *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

/* destroy hints table */
static void
ps_hint_table_done(PS_Hint_Table table,
                   FT_Memory memory)
{
    FT_FREE(table->hints);
    table->num_hints = 0;
    table->max_hints = 0;
}


/* ensure that a table can contain "count" elements */
static FT_Error
ps_hint_table_ensure(PS_Hint_Table table,
                     FT_UInt count,
                     FT_Memory memory)
{
    FT_UInt old_max = table->max_hints;
    FT_UInt new_max = count;
    FT_Error error = PSH_Err_Ok;


    if (new_max > old_max)
    {
        /* try to grow the table */
        new_max = FT_PAD_CEIL(new_max, 8);
        if (!FT_RENEW_ARRAY(table->hints, old_max, new_max))
            table->max_hints = new_max;
    }
    return error;
}


static FT_Error
ps_hint_table_alloc(PS_Hint_Table table,
                    FT_Memory memory,
                    PS_Hint* ahint)
{
    FT_Error error = PSH_Err_Ok;
    FT_UInt count;
    PS_Hint hint = 0;


    count = table->num_hints;
    count++;

    if (count >= table->max_hints)
    {
        error = ps_hint_table_ensure(table, count, memory);
        if (error)
            goto Exit;
    }

    hint = table->hints + count - 1;
    hint->pos = 0;
    hint->len = 0;
    hint->flags = 0;

    table->num_hints = count;

Exit:
    *ahint = hint;
    return error;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                      PS_MASK MANAGEMENT                       *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

/* destroy mask */
static void
ps_mask_done(PS_Mask mask,
             FT_Memory memory)
{
    FT_FREE(mask->bytes);
    mask->num_bits = 0;
    mask->max_bits = 0;
    mask->end_point = 0;
}


/* ensure that a mask can contain "count" bits */
static FT_Error
ps_mask_ensure(PS_Mask mask,
               FT_UInt count,
               FT_Memory memory)
{
    FT_UInt old_max = (mask->max_bits + 7) >> 3;
    FT_UInt new_max = (count + 7) >> 3;
    FT_Error error = PSH_Err_Ok;


    if (new_max > old_max)
    {
        new_max = FT_PAD_CEIL(new_max, 8);
        if (!FT_RENEW_ARRAY(mask->bytes, old_max, new_max))
            mask->max_bits = new_max * 8;
    }
    return error;
}


/* test a bit value in a given mask */
static FT_Int
ps_mask_test_bit(PS_Mask mask,
                 FT_Int idx)
{
    if ((FT_UInt)idx >= mask->num_bits)
        return 0;

    return mask->bytes[idx >> 3] & (0x80 >> (idx & 7));
}


/* clear a given bit */
static void
ps_mask_clear_bit(PS_Mask mask,
                  FT_Int idx)
{
    FT_Byte* p;


    if ((FT_UInt)idx >= mask->num_bits)
        return;

    p = mask->bytes + (idx >> 3);
    p[0] = (FT_Byte)(p[0] & ~(0x80 >> (idx & 7)));
}


/* set a given bit, possibly grow the mask */
static FT_Error
ps_mask_set_bit(PS_Mask mask,
                FT_Int idx,
                FT_Memory memory)
{
    FT_Error error = PSH_Err_Ok;
    FT_Byte* p;


    if (idx < 0)
        goto Exit;

    if ((FT_UInt)idx >= mask->num_bits)
    {
        error = ps_mask_ensure(mask, idx + 1, memory);
        if (error)
            goto Exit;

        mask->num_bits = idx + 1;
    }

    p = mask->bytes + (idx >> 3);
    p[0] = (FT_Byte)(p[0] | (0x80 >> (idx & 7)));

Exit:
    return error;
}


/* destroy mask table */
static void
ps_mask_table_done(PS_Mask_Table table,
                   FT_Memory memory)
{
    FT_UInt count = table->max_masks;
    PS_Mask mask = table->masks;


    for (; count > 0; count--, mask++)
        ps_mask_done(mask, memory);

    FT_FREE(table->masks);
    table->num_masks = 0;
    table->max_masks = 0;
}


/* ensure that a mask table can contain "count" masks */
static FT_Error
ps_mask_table_ensure(PS_Mask_Table table,
                     FT_UInt count,
                     FT_Memory memory)
{
    FT_UInt old_max = table->max_masks;
    FT_UInt new_max = count;
    FT_Error error = PSH_Err_Ok;


    if (new_max > old_max)
    {
        new_max = FT_PAD_CEIL(new_max, 8);
        if (!FT_RENEW_ARRAY(table->masks, old_max, new_max))
            table->max_masks = new_max;
    }
    return error;
}


/* allocate a new mask in a table */
static FT_Error
ps_mask_table_alloc(PS_Mask_Table table,
                    FT_Memory memory,
                    PS_Mask* amask)
{
    FT_UInt count;
    FT_Error error = PSH_Err_Ok;
    PS_Mask mask = 0;


    count = table->num_masks;
    count++;

    if (count > table->max_masks)
    {
        error = ps_mask_table_ensure(table, count, memory);
        if (error)
            goto Exit;
    }

    mask = table->masks + count - 1;
    mask->num_bits = 0;
    mask->end_point = 0;
    table->num_masks = count;

Exit:
    *amask = mask;
    return error;
}


/* return last hint mask in a table, create one if the table is empty */
static FT_Error
ps_mask_table_last(PS_Mask_Table table,
                   FT_Memory memory,
                   PS_Mask* amask)
{
    FT_Error error = PSH_Err_Ok;
    FT_UInt count;
    PS_Mask mask;


    count = table->num_masks;
    if (count == 0)
    {
        error = ps_mask_table_alloc(table, memory, &mask);
        if (error)
            goto Exit;
    }
    else
        mask = table->masks + count - 1;

Exit:
    *amask = mask;
    return error;
}


/* set a new mask to a given bit range */
static FT_Error
ps_mask_table_set_bits(PS_Mask_Table table,
                       const FT_Byte* source,
                       FT_UInt bit_pos,
                       FT_UInt bit_count,
                       FT_Memory memory)
{
    FT_Error error = PSH_Err_Ok;
    PS_Mask mask;


    error = ps_mask_table_last(table, memory, &mask);
    if (error)
        goto Exit;

    error = ps_mask_ensure(mask, bit_count, memory);
    if (error)
        goto Exit;

    mask->num_bits = bit_count;

    /* now, copy bits */
    {
        FT_Byte* read = (FT_Byte*)source + (bit_pos >> 3);
        FT_Int rmask = 0x80 >> (bit_pos & 7);
        FT_Byte* write = mask->bytes;
        FT_Int wmask = 0x80;
        FT_Int val;


        for (; bit_count > 0; bit_count--)
        {
            val = write[0] & ~wmask;

            if (read[0] & rmask)
                val |= wmask;

            write[0] = (FT_Byte)val;

            rmask >>= 1;
            if (rmask == 0)
            {
                read++;
                rmask = 0x80;
            }

            wmask >>= 1;
            if (wmask == 0)
            {
                write++;
                wmask = 0x80;
            }
        }
    }

Exit:
    return error;
}


/* test whether two masks in a table intersect */
static FT_Int
ps_mask_table_test_intersect(PS_Mask_Table table,
                             FT_Int index1,
                             FT_Int index2)
{
    PS_Mask mask1 = table->masks + index1;
    PS_Mask mask2 = table->masks + index2;
    FT_Byte* p1 = mask1->bytes;
    FT_Byte* p2 = mask2->bytes;
    FT_UInt count1 = mask1->num_bits;
    FT_UInt count2 = mask2->num_bits;
    FT_UInt count;


    count = (count1 <= count2) ? count1 : count2;
    for (; count >= 8; count -= 8)
    {
        if (p1[0] & p2[0])
            return 1;

        p1++;
        p2++;
    }

    if (count == 0)
        return 0;

    return (p1[0] & p2[0]) & ~(0xFF >> count);
}


/* merge two masks, used by ps_mask_table_merge_all */
static FT_Error
ps_mask_table_merge(PS_Mask_Table table,
                    FT_Int index1,
                    FT_Int index2,
                    FT_Memory memory)
{
    FT_UInt temp;
    FT_Error error = PSH_Err_Ok;


    /* swap index1 and index2 so that index1 < index2 */
    if (index1 > index2)
    {
        temp = index1;
        index1 = index2;
        index2 = temp;
    }

    if (index1 < index2 && index1 >= 0 && index2 < (FT_Int)table->num_masks)
    {
        /* we need to merge the bitsets of index1 and index2 with a */
        /* simple union                                             */
        PS_Mask mask1 = table->masks + index1;
        PS_Mask mask2 = table->masks + index2;
        FT_UInt count1 = mask1->num_bits;
        FT_UInt count2 = mask2->num_bits;
        FT_Int delta;


        if (count2 > 0)
        {
            FT_UInt pos;
            FT_Byte* read;
            FT_Byte* write;


            /* if "count2" is greater than "count1", we need to grow the */
            /* first bitset, and clear the highest bits                  */
            if (count2 > count1)
            {
                error = ps_mask_ensure(mask1, count2, memory);
                if (error)
                    goto Exit;

                for (pos = count1; pos < count2; pos++)
                    ps_mask_clear_bit(mask1, pos);
            }

            /* merge (unite) the bitsets */
            read = mask2->bytes;
            write = mask1->bytes;
            pos = (FT_UInt)((count2 + 7) >> 3);

            for (; pos > 0; pos--)
            {
                write[0] = (FT_Byte)(write[0] | read[0]);
                write++;
                read++;
            }
        }

        /* Now, remove "mask2" from the list.  We need to keep the masks */
        /* sorted in order of importance, so move table elements.        */
        mask2->num_bits = 0;
        mask2->end_point = 0;

        delta = table->num_masks - 1 - index2; /* number of masks to move */
        if (delta > 0)
        {
            /* move to end of table for reuse */
            PS_MaskRec dummy = *mask2;


            ft_memmove(mask2, mask2 + 1, delta * sizeof(PS_MaskRec));

            mask2[delta] = dummy;
        }

        table->num_masks--;
    }
    else
        FT_TRACE0(("ps_mask_table_merge: ignoring invalid indices (%d,%d)\n",
                   index1, index2));

Exit:
    return error;
}


/* Try to merge all masks in a given table.  This is used to merge */
/* all counter masks into independent counter "paths".             */
/*                                                                 */
static FT_Error
ps_mask_table_merge_all(PS_Mask_Table table,
                        FT_Memory memory)
{
    FT_Int index1, index2;
    FT_Error error = PSH_Err_Ok;


    for (index1 = table->num_masks - 1; index1 > 0; index1--)
    {
        for (index2 = index1 - 1; index2 >= 0; index2--)
        {
            if (ps_mask_table_test_intersect(table, index1, index2))
            {
                error = ps_mask_table_merge(table, index2, index1, memory);
                if (error)
                    goto Exit;

                break;
            }
        }
    }

Exit:
    return error;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                    PS_DIMENSION MANAGEMENT                    *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


/* finalize a given dimension */
static void
ps_dimension_done(PS_Dimension dimension,
                  FT_Memory memory)
{
    ps_mask_table_done(&dimension->counters, memory);
    ps_mask_table_done(&dimension->masks, memory);
    ps_hint_table_done(&dimension->hints, memory);
}


/* initialize a given dimension */
static void
ps_dimension_init(PS_Dimension dimension)
{
    dimension->hints.num_hints = 0;
    dimension->masks.num_masks = 0;
    dimension->counters.num_masks = 0;
}


#if 0

/* set a bit at a given index in the current hint mask */
static FT_Error
ps_dimension_set_mask_bit(PS_Dimension dim,
                          FT_UInt idx,
                          FT_Memory memory)
{
    PS_Mask mask;
    FT_Error error = PSH_Err_Ok;


    /* get last hint mask */
    error = ps_mask_table_last(&dim->masks, memory, &mask);
    if (error)
        goto Exit;

    error = ps_mask_set_bit(mask, idx, memory);

Exit:
    return error;
}

#endif

/* set the end point in a mask, called from "End" & "Reset" methods */
static void
ps_dimension_end_mask(PS_Dimension dim,
                      FT_UInt end_point)
{
    FT_UInt count = dim->masks.num_masks;
    PS_Mask mask;


    if (count > 0)
    {
        mask = dim->masks.masks + count - 1;
        mask->end_point = end_point;
    }
}


/* set the end point in the current mask, then create a new empty one */
/* (called by "Reset" method)                                         */
static FT_Error
ps_dimension_reset_mask(PS_Dimension dim,
                        FT_UInt end_point,
                        FT_Memory memory)
{
    PS_Mask mask;


    /* end current mask */
    ps_dimension_end_mask(dim, end_point);

    /* allocate new one */
    return ps_mask_table_alloc(&dim->masks, memory, &mask);
}


/* set a new mask, called from the "T2Stem" method */
static FT_Error
ps_dimension_set_mask_bits(PS_Dimension dim,
                           const FT_Byte* source,
                           FT_UInt source_pos,
                           FT_UInt source_bits,
                           FT_UInt end_point,
                           FT_Memory memory)
{
    FT_Error error = PSH_Err_Ok;


    /* reset current mask, if any */
    error = ps_dimension_reset_mask(dim, end_point, memory);
    if (error)
        goto Exit;

    /* set bits in new mask */
    error = ps_mask_table_set_bits(&dim->masks, source,
                                   source_pos, source_bits, memory);

Exit:
    return error;
}


/* add a new single stem (called from "T1Stem" method) */
static FT_Error
ps_dimension_add_t1stem(PS_Dimension dim,
                        FT_Int pos,
                        FT_Int len,
                        FT_Memory memory,
                        FT_Int* aindex)
{
    FT_Error error = PSH_Err_Ok;
    FT_UInt flags = 0;


    /* detect ghost stem */
    if (len < 0)
    {
        flags |= PS_HINT_FLAG_GHOST;
        if (len == -21)
        {
            flags |= PS_HINT_FLAG_BOTTOM;
            pos += len;
        }
        len = 0;
    }

    if (aindex)
        *aindex = -1;

    /* now, lookup stem in the current hints table */
    {
        PS_Mask mask;
        FT_UInt idx;
        FT_UInt max = dim->hints.num_hints;
        PS_Hint hint = dim->hints.hints;


        for (idx = 0; idx < max; idx++, hint++)
        {
            if (hint->pos == pos && hint->len == len)
                break;
        }

        /* we need to create a new hint in the table */
        if (idx >= max)
        {
            error = ps_hint_table_alloc(&dim->hints, memory, &hint);
            if (error)
                goto Exit;

            hint->pos = pos;
            hint->len = len;
            hint->flags = flags;
        }

        /* now, store the hint in the current mask */
        error = ps_mask_table_last(&dim->masks, memory, &mask);
        if (error)
            goto Exit;

        error = ps_mask_set_bit(mask, idx, memory);
        if (error)
            goto Exit;

        if (aindex)
            *aindex = (FT_Int)idx;
    }

Exit:
    return error;
}


/* add a "hstem3/vstem3" counter to our dimension table */
static FT_Error
ps_dimension_add_counter(PS_Dimension dim,
                         FT_Int hint1,
                         FT_Int hint2,
                         FT_Int hint3,
                         FT_Memory memory)
{
    FT_Error error = PSH_Err_Ok;
    FT_UInt count = dim->counters.num_masks;
    PS_Mask counter = dim->counters.masks;


    /* try to find an existing counter mask that already uses */
    /* one of these stems here                                */
    for (; count > 0; count--, counter++)
    {
        if (ps_mask_test_bit(counter, hint1) ||
            ps_mask_test_bit(counter, hint2) ||
            ps_mask_test_bit(counter, hint3))
            break;
    }

    /* create a new counter when needed */
    if (count == 0)
    {
        error = ps_mask_table_alloc(&dim->counters, memory, &counter);
        if (error)
            goto Exit;
    }

    /* now, set the bits for our hints in the counter mask */
    error = ps_mask_set_bit(counter, hint1, memory);
    if (error)
        goto Exit;

    error = ps_mask_set_bit(counter, hint2, memory);
    if (error)
        goto Exit;

    error = ps_mask_set_bit(counter, hint3, memory);
    if (error)
        goto Exit;

Exit:
    return error;
}


/* end of recording session for a given dimension */
static FT_Error
ps_dimension_end(PS_Dimension dim,
                 FT_UInt end_point,
                 FT_Memory memory)
{
    /* end hint mask table */
    ps_dimension_end_mask(dim, end_point);

    /* merge all counter masks into independent "paths" */
    return ps_mask_table_merge_all(&dim->counters, memory);
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                    PS_RECORDER MANAGEMENT                     *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


/* destroy hints */
FT_LOCAL(void)
ps_hints_done(PS_Hints hints)
{
    FT_Memory memory = hints->memory;


    ps_dimension_done(&hints->dimension[0], memory);
    ps_dimension_done(&hints->dimension[1], memory);

    hints->error = PSH_Err_Ok;
    hints->memory = 0;
}


FT_LOCAL(FT_Error)
ps_hints_init(PS_Hints hints,
              FT_Memory memory)
{
    FT_MEM_ZERO(hints, sizeof(*hints));
    hints->memory = memory;
    return PSH_Err_Ok;
}


/* initialize a hints for a new session */
static void
ps_hints_open(PS_Hints hints,
              PS_Hint_Type hint_type)
{
    switch (hint_type)
    {
        case PS_HINT_TYPE_1:
        case PS_HINT_TYPE_2:
            hints->error = PSH_Err_Ok;
            hints->hint_type = hint_type;

            ps_dimension_init(&hints->dimension[0]);
            ps_dimension_init(&hints->dimension[1]);
            break;

        default:
            hints->error = PSH_Err_Invalid_Argument;
            hints->hint_type = hint_type;

            FT_TRACE0(("ps_hints_open: invalid charstring type\n"));
            break;
    }
}


/* add one or more stems to the current hints table */
static void
ps_hints_stem(PS_Hints hints,
              FT_Int dimension,
              FT_UInt count,
              FT_Long* stems)
{
    if (!hints->error)
    {
        /* limit "dimension" to 0..1 */
        if (dimension < 0 || dimension > 1)
        {
            FT_TRACE0(("ps_hints_stem: invalid dimension (%d) used\n",
                       dimension));
            dimension = (dimension != 0);
        }

        /* record the stems in the current hints/masks table */
        switch (hints->hint_type)
        {
            case PS_HINT_TYPE_1: /* Type 1 "hstem" or "vstem" operator */
            case PS_HINT_TYPE_2: /* Type 2 "hstem" or "vstem" operator */
            {
                PS_Dimension dim = &hints->dimension[dimension];


                for (; count > 0; count--, stems += 2)
                {
                    FT_Error error;
                    FT_Memory memory = hints->memory;


                    error = ps_dimension_add_t1stem(
                        dim, (FT_Int)stems[0], (FT_Int)stems[1],
                        memory, NULL);
                    if (error)
                    {
                        FT_ERROR(("ps_hints_stem: could not add stem"
                                  " (%d,%d) to hints table\n", stems[0], stems[1]));

                        hints->error = error;
                        return;
                    }
                }
                break;
            }

            default:
                FT_TRACE0(("ps_hints_stem: called with invalid hint type (%d)\n",
                           hints->hint_type));
                break;
        }
    }
}


/* add one Type1 counter stem to the current hints table */
static void
ps_hints_t1stem3(PS_Hints hints,
                 FT_Int dimension,
                 FT_Fixed* stems)
{
    FT_Error error = PSH_Err_Ok;


    if (!hints->error)
    {
        PS_Dimension dim;
        FT_Memory memory = hints->memory;
        FT_Int count;
        FT_Int idx[3];


        /* limit "dimension" to 0..1 */
        if (dimension < 0 || dimension > 1)
        {
            FT_TRACE0(("ps_hints_t1stem3: invalid dimension (%d) used\n",
                       dimension));
            dimension = (dimension != 0);
        }

        dim = &hints->dimension[dimension];

        /* there must be 6 elements in the 'stem' array */
        if (hints->hint_type == PS_HINT_TYPE_1)
        {
            /* add the three stems to our hints/masks table */
            for (count = 0; count < 3; count++, stems += 2)
            {
                error = ps_dimension_add_t1stem(dim,
                                                (FT_Int)FIXED_TO_INT(stems[0]),
                                                (FT_Int)FIXED_TO_INT(stems[1]),
                                                memory, &idx[count]);
                if (error)
                    goto Fail;
            }

            /* now, add the hints to the counters table */
            error = ps_dimension_add_counter(dim, idx[0], idx[1], idx[2],
                                             memory);
            if (error)
                goto Fail;
        }
        else
        {
            FT_ERROR(("ps_hints_t1stem3: called with invalid hint type\n"));
            error = PSH_Err_Invalid_Argument;
            goto Fail;
        }
    }

    return;

Fail:
    FT_ERROR(("ps_hints_t1stem3: could not add counter stems to table\n"));
    hints->error = error;
}


/* reset hints (only with Type 1 hints) */
static void
ps_hints_t1reset(PS_Hints hints,
                 FT_UInt end_point)
{
    FT_Error error = PSH_Err_Ok;


    if (!hints->error)
    {
        FT_Memory memory = hints->memory;


        if (hints->hint_type == PS_HINT_TYPE_1)
        {
            error = ps_dimension_reset_mask(&hints->dimension[0],
                                            end_point, memory);
            if (error)
                goto Fail;

            error = ps_dimension_reset_mask(&hints->dimension[1],
                                            end_point, memory);
            if (error)
                goto Fail;
        }
        else
        {
            /* invalid hint type */
            error = PSH_Err_Invalid_Argument;
            goto Fail;
        }
    }
    return;

Fail:
    hints->error = error;
}


/* Type2 "hintmask" operator, add a new hintmask to each direction */
static void
ps_hints_t2mask(PS_Hints hints,
                FT_UInt end_point,
                FT_UInt bit_count,
                const FT_Byte* bytes)
{
    FT_Error error;


    if (!hints->error)
    {
        PS_Dimension dim = hints->dimension;
        FT_Memory memory = hints->memory;
        FT_UInt count1 = dim[0].hints.num_hints;
        FT_UInt count2 = dim[1].hints.num_hints;


        /* check bit count; must be equal to current total hint count */
        if (bit_count != count1 + count2)
        {
            FT_TRACE0(("ps_hints_t2mask:"
                       " called with invalid bitcount %d (instead of %d)\n",
                       bit_count, count1 + count2));

            /* simply ignore the operator */
            return;
        }

        /* set-up new horizontal and vertical hint mask now */
        error = ps_dimension_set_mask_bits(&dim[0], bytes, count2, count1,
                                           end_point, memory);
        if (error)
            goto Fail;

        error = ps_dimension_set_mask_bits(&dim[1], bytes, 0, count2,
                                           end_point, memory);
        if (error)
            goto Fail;
    }
    return;

Fail:
    hints->error = error;
}


static void
ps_hints_t2counter(PS_Hints hints,
                   FT_UInt bit_count,
                   const FT_Byte* bytes)
{
    FT_Error error;


    if (!hints->error)
    {
        PS_Dimension dim = hints->dimension;
        FT_Memory memory = hints->memory;
        FT_UInt count1 = dim[0].hints.num_hints;
        FT_UInt count2 = dim[1].hints.num_hints;


        /* check bit count, must be equal to current total hint count */
        if (bit_count != count1 + count2)
        {
            FT_TRACE0(("ps_hints_t2counter:"
                       " called with invalid bitcount %d (instead of %d)\n",
                       bit_count, count1 + count2));

            /* simply ignore the operator */
            return;
        }

        /* set-up new horizontal and vertical hint mask now */
        error = ps_dimension_set_mask_bits(&dim[0], bytes, 0, count1,
                                           0, memory);
        if (error)
            goto Fail;

        error = ps_dimension_set_mask_bits(&dim[1], bytes, count1, count2,
                                           0, memory);
        if (error)
            goto Fail;
    }
    return;

Fail:
    hints->error = error;
}


/* end recording session */
static FT_Error
ps_hints_close(PS_Hints hints,
               FT_UInt end_point)
{
    FT_Error error;


    error = hints->error;
    if (!error)
    {
        FT_Memory memory = hints->memory;
        PS_Dimension dim = hints->dimension;


        error = ps_dimension_end(&dim[0], end_point, memory);
        if (!error)
        {
            error = ps_dimension_end(&dim[1], end_point, memory);
        }
    }

    #ifdef DEBUG_HINTER
    if (!error)
        ps_debug_hints = hints;
    #endif
    return error;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                TYPE 1 HINTS RECORDING INTERFACE               *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

static void
t1_hints_open(T1_Hints hints)
{
    ps_hints_open((PS_Hints)hints, PS_HINT_TYPE_1);
}

static void
t1_hints_stem(T1_Hints hints,
              FT_Int dimension,
              FT_Fixed* coords)
{
    FT_Pos stems[2];


    stems[0] = FIXED_TO_INT(coords[0]);
    stems[1] = FIXED_TO_INT(coords[1]);

    ps_hints_stem((PS_Hints)hints, dimension, 1, stems);
}


FT_LOCAL_DEF(void)
t1_hints_funcs_init(T1_Hints_FuncsRec * funcs)
{
    FT_MEM_ZERO((char*)funcs, sizeof(*funcs));

    funcs->open = (T1_Hints_OpenFunc)t1_hints_open;
    funcs->close = (T1_Hints_CloseFunc)ps_hints_close;
    funcs->stem = (T1_Hints_SetStemFunc)t1_hints_stem;
    funcs->stem3 = (T1_Hints_SetStem3Func)ps_hints_t1stem3;
    funcs->reset = (T1_Hints_ResetFunc)ps_hints_t1reset;
    funcs->apply = (T1_Hints_ApplyFunc)ps_hints_apply;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                TYPE 2 HINTS RECORDING INTERFACE               *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

static void
t2_hints_open(T2_Hints hints)
{
    ps_hints_open((PS_Hints)hints, PS_HINT_TYPE_2);
}


static void
t2_hints_stems(T2_Hints hints,
               FT_Int dimension,
               FT_Int count,
               FT_Fixed* coords)
{
    FT_Pos stems[32], y, n;
    FT_Int total = count;


    y = 0;
    while (total > 0)
    {
        /* determine number of stems to write */
        count = total;
        if (count > 16)
            count = 16;

        /* compute integer stem positions in font units */
        for (n = 0; n < count * 2; n++)
        {
            y += coords[n];
            stems[n] = FIXED_TO_INT(y);
        }

        /* compute lengths */
        for (n = 0; n < count * 2; n += 2)
            stems[n + 1] = stems[n + 1] - stems[n];

        /* add them to the current dimension */
        ps_hints_stem((PS_Hints)hints, dimension, count, stems);

        total -= count;
    }
}


FT_LOCAL_DEF(void)
t2_hints_funcs_init(T2_Hints_FuncsRec * funcs)
{
    FT_MEM_ZERO(funcs, sizeof(*funcs));

    funcs->open = (T2_Hints_OpenFunc)t2_hints_open;
    funcs->close = (T2_Hints_CloseFunc)ps_hints_close;
    funcs->stems = (T2_Hints_StemsFunc)t2_hints_stems;
    funcs->hintmask = (T2_Hints_MaskFunc)ps_hints_t2mask;
    funcs->counter = (T2_Hints_CounterFunc)ps_hints_t2counter;
    funcs->apply = (T2_Hints_ApplyFunc)ps_hints_apply;
}


/* END */